package sandbox.patterns.stream;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * Understands a (possily) infinite sequence of elements
 */
public class Stream<E> implements Iterable<E> {

    private final E head;
    private final TailGenerator<E> tailGenerator;  //TODO: rename

    public Stream(E head, TailGenerator<E> tailGenerator) {
        this(head, tailGenerator, false);
    }

    private Stream(E head, TailGenerator<E> tailGenerator, boolean emptyStream) {
        if(!emptyStream && head == null) throw new IllegalArgumentException();

        this.head = head;
        this.tailGenerator = tailGenerator;
    }

    public E head() { return head; }
    public Stream<E> tail() { return tailGenerator.makeTail(head()); }

    @Override
    public Iterator<E> iterator() { return new StreamIterator<E>(this); }

    public List<E> take(int required) {
        if(required < 0)
            throw new IllegalArgumentException("cannot take less than zero elements; rquested: " + required);

        List<E> taken = new LinkedList<E>();

        for (E current : this) {
            if(taken.size() < required)taken.add(current);
            else break;
        }

        return taken;
    }

    public Stream<E> filter(final Predicate<E> condition) {
        if(condition.accepts(head)) return new Stream<E>(head, new TailGenerator<E>() {
            Predicate<E> cond = condition;
            public Stream<E> makeTail(E head) {
                return tail().filter(cond);
            }
        });
        else return tail().filter(condition);
    }

    /**
     * Understands tail sequences
     */
    public interface TailGenerator<T> {
        public Stream<T> makeTail(T head);
    }

    private static final TailGenerator emptyTailGenerator = new  TailGenerator() {
        public Stream makeTail(Object head) { return Stream.empty(); }
    };

    @SuppressWarnings("unchecked")
    private static final Stream EMPTY = new Stream(null, emptyTailGenerator, true);

    @SuppressWarnings("unchecked")
    public static <T> Stream<T> empty() { return (Stream<T>) Stream.EMPTY; }

    public static <T> Stream<T> over(final List<T> list) {
        Stream<T> emptyStream = Stream.empty();
        return list.size() == 0 ?
            emptyStream :
            new Stream<T>(list.get(0), new TailGenerator<T>() {
                public Stream<T> makeTail(T head) {
                    return Stream.over(list.subList(1, list.size()));
                }
            });
    }
}
